#include"file_ops.hpp"
#include<sstream>
#include<boost/date_time/posix_time/posix_time.hpp>
#include<boost/move/move.hpp>
#include<boost/python.hpp>
#include"ork/prereq.hpp"
#include"ork/tools.hpp"
#include"ork/log.hpp"
#include"ork/file_system.hpp"

#if ORK_LINUX
#include<sys/types.h>
#include<attr/xattr.h>
#include"gcrypt.h"
#else//Some stubs to be able to compile on Windows
size_t getxattr(const char*path,const char*name,void*value,size_t size){return 0;}
size_t lgetxattr(const char*path,const char*name,void*value,size_t size){
	std::string val("Some attribute");
	memcpy(value,val.c_str(),val.size()+1);
	return val.size();
}
size_t fgetxattr(int fd,const char*name,void*value,size_t size){return 0;}
size_t listxattr(const char *path, char *list, size_t size){return 0;}
size_t llistxattr(const char *path, char *list, size_t size){return 0;}
size_t flistxattr(int fd, char *list, size_t size){return 0;}
int setxattr(const char*path,const char*name,const void*value,size_t size,int flags){return 0;}
int lsetxattr(const char*path,const char*name,const void*value,size_t size,int flags){return 0;}
int fsetxattr(int fd,const char*name,const void*value,size_t size,int flags){return 0;}
#endif

#define NO_CRYPT 1
#if ORK_TEST
#define XATTR_DEBUG_LOG 1
#endif

namespace xattr{

#if PARSER_EMU
	attribute_list parser(const std::string&log_file){
		attribute_list list;
		LOOPI(3){
			std::stringstream s;
			s<<"File #"<<i;
			list.push_back(file_attribute(s.str()));
			LOOPJ(3){
				std::stringstream name;
				name<<"user "<<j;
				namespace time=boost::posix_time;
				list.back().attributes.push_back(attribute_t(name.str(),time::to_simple_string(time::second_clock::local_time())));
			}
		}
		return boost::move(list);
	}
#endif


namespace detail{
	struct trust_policy{
	protected:
		bool m_trusted;
	public:
		trust_policy():m_trusted(true){}
	public:
		void operator()(const boost::filesystem::path&p){m_trusted&=eval(p);}
		bool trusted(){return m_trusted;}
		static bool eval(const boost::filesystem::path&p){
			if(boost::filesystem::is_directory(p)){
				return true;//TODO: placeholder
			}else if(boost::filesystem::is_symlink(p)){
				return true;//TODO: placeholder
			}else if(boost::filesystem::is_regular_file(p)){
				return true;//TODO: placeholder
			}else if(boost::filesystem::is_other(p))return false;//Not sure what to do here
			else if(boost::filesystem::is_empty(p))return false;//Something is fishy if we are called with an empty file
			else return false;
		}
	};
}


namespace crypt{
	const std::string KEY_FILE("./key.bin");

	#if ORK_LINUX && 0
	int GeneratePublicKeyPair(char*privfile,char*pubfile,int type){
		gcry_sexp_t key=NULL,parms=NULL;
		char dsa_keybuf[]="(genkey(dsa(nbits 4:2048)))";//(qbits)
		char rsa_keybuf[]="(genkey(rsa(nbits 4:2048)))";//(transient-key)
		int rtn=0;

		switch( type ) {
		case DSA_KEY_TYPE:
			gcry_sexp_build (&parms, NULL, dsa_keybuf);
			break;
		case RSA_KEY_TYPE:
			gcry_sexp_build (&parms, NULL, rsa_keybuf);
			break;
		default:
			printf("GeneratePublicKeyPair: unrecognized key type\n");
			return -1;
		}

		if ( parms == NULL ) {
			printf("GeneratePublicKeyPair: parm generation failed\n");
			return -1;
		}			    

		/* Generate Key Pair */
		if (!gcry_pk_genkey(&key,parms)){
			gcry_sexp_t subsexp;
			/* get private key and write to privfile */
			subsexp = gcry_sexp_find_token( key, "private-key", strlen("private-key") );
			if(!gcry_pk_testkey(key)){
				write_sexp_to_file( privfile, &subsexp, FILE_CLEAR );
			}
			else {
				printf("GeneratePublicKeyPair: private key fails testkey -- not written\n");
				rtn = -1;
			}
			gcry_sexp_release( subsexp );
			if(rtn) goto end;

			/* get public key and write to pubfile */
			subsexp = gcry_sexp_find_token( key, "public-key", strlen("public-key") );
			write_sexp_to_file( pubfile, &subsexp, FILE_CLEAR );

			gcry_sexp_release( subsexp );
		}
		else {
			printf("GeneratePublicKeyPair: failed key gen -- not written\n");
			rtn = -1;
		}
	end:
		if(parms)gcry_sexp_release(parms);
		if(key)gcry_sexp_release(key);
		return rtn;
	}
	#endif
	std::string gen_key(){
		#if ORK_LINUX && !NO_CRYPT
			#error
			//TODO: Placeholder
		#else//Some stubs to be able to compile on Windows
			return "This is a 32-byte key that works";
		#endif
	}
	//void save_key(const std::string&key){
	//	ork::ensure_file(KEY_FILE);
	//	std::ofstream fout(KEY_FILE);
	//	ORK_ASSERT(fout);
	//	ork::string_out(fout,key);
	//}
	std::string load_key(){
		if(!boost::filesystem::exists(KEY_FILE)){
			std::string key=gen_key();
			ork::ensure_directory(KEY_FILE);
			std::ofstream fout(KEY_FILE);
			ORK_ASSERT(fout);
			ork::string_out(fout,key);
		}
		std::string key;
		ork::IFStream fin(KEY_FILE);
		ork::string_in(fin,key);
		return boost::move(key);
	}

	std::string encrypt(const std::string&data){//TODO: Placeholder
		std::string key=load_key();
		#if ORK_LINUX && !NO_CRYPT
			#error
		#else//Some stubs to be able to compile on Windows
			return data;//Null encryption
		#endif
	}
	std::string decrypt(const std::string&cypher){//TODO: Placeholder
		std::string key=load_key();
		#if ORK_LINUX && !NO_CRYPT
			#error
		#else//Some stubs to be able to compile on Windows
			return cypher;//Null encryption
		#endif
	}
	std::string hash(const std::string&data){
		#if ORK_LINUX && !NO_CRYPT
			ORK_ASSERT(gcry_check_version(GCRYPT_VERSION));
			gcry_control(GCRYCTL_DISABLE_SECMEM,0); 
			gcry_control(GCRYCTL_INITIALIZATION_FINISHED,0); 
			//SHA1 for now (only 160 bits)
			std::string hash(20,'\0');//32 bytes=256 bits
			gcry_md_hash_buffer(GCRY_MD_SHA1,const_cast<char*>(hash.data()),const_cast<char*>(data.data()),data.size());
			return boost::move(hash);
		#else//Some stubs to be able to compile on Windows
			return data;
		#endif
	}
}


namespace file{
	const std::string NAME_SCOPE("system.attrust.");
	const std::string SIG_NAME(NAME_SCOPE+"signature");
	const size_t MAX_ATTR_SIZE=64;//32 chars for xattr and 32 chars (256-bit) for hash
	const size_t MAX_LIST_SIZE=0x1<<12;//2^6*2^6=64*64=4K chars is enough?


	//Low-level function to write an xattr to a file
	bool write_xattr(const boost::filesystem::path&file,const std::string&attr_name,const std::string&attr_val){
		using namespace ork;
		if(!boost::filesystem::exists(file))return false;
		std::string name(NAME_SCOPE+attr_name);
		#if XATTR_DEBUG_LOG
		ORK_WRITE_MSG("write_xattr: writing \""<<attr_val<<'\"')
		#endif
		return 0==lsetxattr(
			file.generic_string().c_str()
			,name.c_str()
			,attr_val.c_str()
			,attr_val.size()
			,0//This flag can be either XATTR_CREATE or XATTR_REPLACE, default (0) will create OR replace
		);
	}
	//High-level function to write a protected xattr
	bool write_crypt_attr(const boost::filesystem::path&file,const std::string&attr_name,const std::string&attr_val){
		//TODO: include hash in the value, with some delimeter or pascal-style size
		std::stringstream stm;
		ork::string_out(stm,attr_val);
		//TODO: Add delimeter here? The next is binary.
		const std::string cypher=crypt::encrypt(attr_val);
		ork::string_out(stm,cypher);
		#if XATTR_DEBUG_LOG
		using namespace ork;
		ORK_WRITE_MSG("write_crypt_attr: data was \""<<attr_val<<"\" cypher was \""<<cypher<<'\"')
		#endif
		return write_xattr(file,attr_name,stm.str());
	}


	//Low-level function to list extended attributes.  Returns true if the file is found (even if list is empty)
	bool read_xattr_list(const boost::filesystem::path&file,std::string&list){
		using namespace ork;
		if(!boost::filesystem::exists(file))return false;
		list.clear();
		list.resize(MAX_LIST_SIZE,'\0');
		const size_t num_read=llistxattr(file.generic_string().c_str(),const_cast<char*>(list.data()),list.size());
		ORK_ASSERT(num_read<MAX_LIST_SIZE);
		if(num_read==0)return false;
		list.resize(num_read);
		return true;
	}
	//Low-level function to read an extended attribute. Returns true if the file and xattr are found
	bool read_xattr(const boost::filesystem::path&file,const std::string&attr_name,std::string&val){
		using namespace ork;
		if(!boost::filesystem::exists(file))return false;
		std::string name(NAME_SCOPE+attr_name);
		val.clear();
		val.resize(MAX_ATTR_SIZE,'\0');
		const size_t num_read=lgetxattr(file.generic_string().c_str(),name.c_str(),const_cast<char*>(val.data()),val.size());
		ORK_ASSERT(num_read<MAX_ATTR_SIZE);
		if(num_read==0)return false;
		val.resize(num_read);
		#if XATTR_DEBUG_LOG
		ORK_WRITE_MSG("read_xattr: read \""<<val<<'\"')
		#endif
		return true;
	}
	//High-level function to read a protected xattr
	//First return is read success, second is read&&encryption success
	std::tuple<bool,bool>read_crypt_attr(const boost::filesystem::path&file,const std::string&attr_name,std::string&val){
		std::string crypt;
		if(!read_xattr(file,attr_name,crypt))return std::make_tuple(false,false);
		std::stringstream stm(crypt);
		ork::string_in(stm,val);
		//TODO: Add delimeter here? The next is binary.
		ork::string_in(stm,crypt);
		const std::string plain=crypt::decrypt(crypt);
		if(val!=plain)return std::make_tuple(true,false);//The signature does not match
		#if XATTR_DEBUG_LOG
		using namespace ork;
		ORK_WRITE_MSG("write_crypt_attr: data was \""<<val<<"\" plaintext was \""<<plain<<'\"')
		#endif
		return std::make_tuple(true,true);
	}
	bool read_attribute(const std::string&path,const std::string&attr_name,std::string&val,bool&trust_it){
		std::tuple<bool,bool>retval=read_crypt_attr(path,attr_name,val);
		trust_it=std::get<1>(retval);
		return std::get<0>(retval);
	}


	//Returns true if all attribute_t were successfully written.
	//This is not atomic, in that a subset of attribute_t might be written.
	bool write_attributes(const file_attribute&fa){
		bool success=true;
		//Do not encrypt attributes here, commit as many as we can below at the cost of encrypting the count for each attribute
		LOOPVI(fa.attributes)success&=write_attribute(fa.file_name,fa.attributes[i].user_id,fa.attributes[i].timestamp);
		return success;
	}
	//Returns true if all attributes were successfully written.
	//If false, the list contains only those file_attribute that could not be written
	attribute_list write_attribute_list(const attribute_list&list){
		attribute_list failed_list;
		LOOPVI(list)if(!write_attributes(list[i]))failed_list.push_back(list[i]);//Just loop through the list
		return boost::move(failed_list);
	}


	//We do not verify the hash here, only retrieve the attributes.
	//string used because this is an interface function (no boost in interface)
	bool read_attributes(const std::string&file_name,file_attribute&file_attr){
		using namespace ork;
		const boost::filesystem::path file(file_name);
		if(!boost::filesystem::exists(file))return false;
		std::string list;
		read_xattr_list(file,list);
		std::stringstream ls(list);
		file_attribute temp_attr(file_name);//We do not modify the passed list unless we succeed
		std::string cur_name;
		while(std::getline(ls,cur_name,'\0'))if(cur_name.find(NAME_SCOPE)!=std::string::npos){
			std::string val;
			#if !NO_CRYPT
				std::tuple<bool,bool>read_succ=read_crypt_attr(file,cur_name,val);
				bool succ=std::get<0>(read_succ);//TODO: What to do here?
			#else
				bool succ=read_xattr(file,cur_name,val);
			#endif
			ORK_ASSERT(succ);//What to do if we read the name and then cannot read the value?
			const size_t begin=cur_name.find_last_of('.');//Trim name scope
			temp_attr.attributes.push_back(attribute_t(cur_name.substr(begin,std::string::npos),val));
		}
		file_attr=boost::move(temp_attr);
		return true;
	}
	//Returns true if the attribute_t was successfully written.
	//TODO: Distinguish read failure and protection violation
	bool write_attribute(const std::string&file_path,const std::string&attr_name,const std::string&val){
		const boost::filesystem::path&file(file_path);
		std::string temp;
		bool existed=read_xattr(file,attr_name,temp);//Check if the xattr already exists, so we can increment count later
		//TODO: We should read and update parity here instead of count
		#if !NO_CRYPT
			bool attr_succ=write_crypt_attr(file,attr_nam,val);
		#else
			bool attr_succ=write_xattr(file,attr_name,val);
		#endif
		if(!attr_succ)return false;
		if(!existed){//Increment the count
			std::string count_s;
			#if !NO_CRYPT
				std::tuple<bool,bool>encr_succ=read_crypt_attr(file,SIG_NAME,count_s);
				//TODO: Assert count exists or no scoped attribute exists. What about an adversary just deleting the entire xattr?
				//ORK_ASSERT(std::get<0>(attr_succ));
				//if(std::get<0>(attr_succ)&&!std::get<1>(attr_succ));//We have been hacked, maybe
				//if(!std::get<0>(attr_succ)||!std::get<1>(attr_succ))return false;
				bool count_succ=std::get<0>(attr_succ);//TODO: Placeholder
			#else
				bool count_succ=read_xattr(file,SIG_NAME,count_s);
			#endif
			ORK_ASSERT(count_succ);//For now we do not handle errors or rollback
			//if(!count_succ){
			//	TODO: Roll back attribute write if fail to write count (save it above)
			//	return false;
			//}
			size_t old_count;
			std::stringstream cs(count_s);
			cs>>old_count;
			ORK_ASSERT(cs);
			#if !NO_CRYPT
				count_succ=write_crypt_attr(file,SIG_NAME,boost::lexical_cast<std::string>(++old_count));
			#else
				count_succ=write_xattr(file,SIG_NAME,boost::lexical_cast<std::string>(++old_count));
			#endif
			ORK_ASSERT(count_succ);//For now we do not handle errors or rollback
			//TODO: give up and throw exception if we are stuck with failed write and failed rollback of count?
		}
		return true;
	}

}//namespace file


//Just fetch the history attributes
bool get_history(const std::string&file_name,file_attribute&fa){return file::read_attributes(file_name,fa);}
//Tests a single file (or directory), if a link/directory tests the link/directory itself
bool trust_file(const std::string&file_name){return detail::trust_policy::eval(file_name);}
//Test everything in a directory (including subdirectories), but do not go into subdirectories
bool trust_directory(const std::string&file_name){
	detail::trust_policy policy;
	ork::iterate_directory<ork::flat_search,detail::trust_policy>dir;
	dir(file_name);
	return policy.trusted();
}
//Test everything in a directory (including subdirectories), and recurse into subdirectories
bool trust_tree(const std::string&file_name){
	detail::trust_policy policy;
	ork::iterate_directory<ork::recursive_search,detail::trust_policy>dir;
	dir(file_name);
	return policy.trusted();
}


namespace python{
	bool write_attr_arr(
		const size_t path_len,const char*const path
		,const size_t name_len,const char*const name
		,const size_t val_len,const char*const val
	){return file::write_attribute(std::string(path,path_len),std::string(name,name_len),std::string(val,val_len));}
	bool write_attr_str(const char*const path,const char*const name,const char*const val)
		{return file::write_attribute(path,name,val);}
	bool read_attr_arr(
		const size_t path_len,const char*const path
		,const size_t name_len,const char*const name
		,size_t*const val_len,char*const val
		,bool*const trust_it
	){
		ORK_ASSERT(trust_it!=nullptr);
		ORK_ASSERT(val_len!=nullptr);
		std::string temp;
		const bool succ=file::read_attribute(std::string(path,path_len),std::string(name,name_len),temp,*trust_it);
		ORK_ASSERT(*val_len>=temp.size());
		*val_len=temp.size();
		memcpy(val,temp.c_str(),temp.size()+1);//+1 for NULL
		return succ;
	}
	bool read_attr_str(const char*const path,const char*const name,char*const val,bool*const trust_it){
		ORK_ASSERT(trust_it!=nullptr);
		std::string temp;
		const bool succ=file::read_attribute(path,name,temp,*trust_it);
		memcpy(val,temp.c_str(),temp.size()+1);//+1 for NULL
		return succ;
	}
}//namespace python
}//namespace xattr

BOOST_PYTHON_MODULE(xattr){
	boost::python::def("write_attr_arr",xattr::python::write_attr_arr);
	boost::python::def("write_attr_str",xattr::python::write_attr_str);
	boost::python::def("read_attr_arr",xattr::python::read_attr_arr);
	boost::python::def("read_attr_str",xattr::python::read_attr_str);
}
